use super::*;

/// A [lookup table](
/// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html).
///
/// u32 values in Format10 tables will be truncated to u16.
/// u64 values in Format10 tables are not supported.
#[derive(Clone)]
pub enum Lookup<'a> {
	Format0(LazyArray16<'a, u16>),
	Format2(BinarySearchTable<'a, LookupSegment>),
	Format4(BinarySearchTable<'a, LookupSegment>, &'a [u8]),
	Format6(BinarySearchTable<'a, LookupSingle>),
	Format8 {
		first_glyph: u16,
		values: LazyArray16<'a, u16>,
	},
	Format10 {
		value_size: u16,
		first_glyph: u16,
		glyph_count: u16,
		data: &'a [u8],
	},
}
impl<'a> Lookup<'a> {
	/// Parses a lookup table from raw data.
	///
	/// `num_glyphs` is from the `maxp` table.
	#[inline]
	pub fn parse(num_glyphs: u16, data: &'a [u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		let format = s.read::<u16>()?;
		match format {
			0 => Some(Self::Format0(s.read_array16::<u16>(num_glyphs)?)),
			2 => Some(Self::Format2(BinarySearchTable::<LookupSegment>::parse(s.tail()?)?)),
			4 => Some(Self::Format4(BinarySearchTable::<LookupSegment>::parse(s.tail()?)?, data)),
			6 => Some(Self::Format6(BinarySearchTable::<LookupSingle>::parse(s.tail()?)?)),
			8 => Some(Self::Format8 {
				first_glyph: s.read::<u16>()?,
				values: s.parse_array16::<u16>()?,
			}),
			10 => Some(Self::Format10 {
				value_size: s.read::<u16>()?,
				first_glyph: s.read::<u16>()?,
				glyph_count: s.read::<u16>()?,
				data: s.tail()?,
			}),
			_ => None,
		}
	}
	/// Returns a value associated with the specified glyph.
	#[inline]
	pub fn get(&self, glyph_id: GlyphId) -> Option<u16> {
		match self {
			Self::Format0(values) => values.get(glyph_id.0),
			Self::Format2(ref bsearch) => bsearch.get(glyph_id).map(|v| v.value),
			Self::Format4(ref bsearch, data) => {
				// In format 4, LookupSegment contains an offset to a list of u16 values.
				// One value for each glyph in the LookupSegment range.
				let segment = bsearch.get(glyph_id)?;
				let index = glyph_id.0.checked_sub(segment.first_glyph)?;
				let offset = usize::from(segment.value) + u16::SIZE * usize::from(index);
				Stream::read_at::<u16>(data, offset)
			}
			Self::Format6(ref bsearch) => bsearch.get(glyph_id).map(|v| v.value),
			Self::Format8 { first_glyph, values } => {
				let idx = glyph_id.0.checked_sub(*first_glyph)?;
				values.get(idx)
			}
			Self::Format10 {
				value_size,
				first_glyph,
				glyph_count,
				data,
			} => {
				let idx = glyph_id.0.checked_sub(*first_glyph)?;
				let mut s = Stream::new(data);
				match value_size {
					1 => s.read_array16::<u8>(*glyph_count)?.get(idx).map(u16::from),
					2 => s.read_array16::<u16>(*glyph_count)?.get(idx),
					// TODO: we should return u32 here, but this is not supported yet
					4 => s.read_array16::<u32>(*glyph_count)?.get(idx).map(|n| n as u16),
					_ => None, // 8 is also supported
				}
			}
		}
	}
}
impl core::fmt::Debug for Lookup<'_> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Format0(arg0) => f.debug_tuple("Format0").field(arg0).finish(),
			Self::Format2(arg0) => f.debug_tuple("Format2").field(arg0).finish(),
			Self::Format4(arg0, arg1) => f.debug_tuple("Format4").field(arg0).field(arg1).finish(),
			Self::Format6(arg0) => f.debug_tuple("Format6").field(arg0).finish(),
			Self::Format8 { first_glyph, values } => f
				.debug_struct("Format8")
				.field("first_glyph", first_glyph)
				.field("values", values)
				.finish(),
			Self::Format10 {
				value_size,
				first_glyph,
				glyph_count,
				data,
			} => f
				.debug_struct("Format10")
				.field("value_size", value_size)
				.field("first_glyph", first_glyph)
				.field("glyph_count", glyph_count)
				.field("data", data)
				.finish(),
		}
	}
}

#[derive(Clone, Copy, Debug)]
pub struct LookupSegment {
	pub last_glyph: u16,
	pub first_glyph: u16,
	pub value: u16,
}
impl FromData for LookupSegment {
	const SIZE: usize = 6;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		Some(LookupSegment {
			last_glyph: s.read::<u16>()?,
			first_glyph: s.read::<u16>()?,
			value: s.read::<u16>()?,
		})
	}
}
impl BinarySearchValue for LookupSegment {
	#[inline]
	fn is_termination(&self) -> bool {
		self.last_glyph == 0xFFFF && self.first_glyph == 0xFFFF
	}
	#[inline]
	fn contains(&self, id: GlyphId) -> core::cmp::Ordering {
		if id.0 < self.first_glyph {
			core::cmp::Ordering::Less
		} else if id.0 <= self.last_glyph {
			core::cmp::Ordering::Equal
		} else {
			core::cmp::Ordering::Greater
		}
	}
}

#[derive(Clone, Copy, Debug)]
pub struct LookupSingle {
	pub glyph: u16,
	pub value: u16,
}
impl FromData for LookupSingle {
	const SIZE: usize = 4;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		Some(LookupSingle {
			glyph: s.read::<u16>()?,
			value: s.read::<u16>()?,
		})
	}
}
impl BinarySearchValue for LookupSingle {
	#[inline]
	fn is_termination(&self) -> bool {
		self.glyph == 0xFFFF
	}
	#[inline]
	fn contains(&self, id: GlyphId) -> core::cmp::Ordering {
		id.0.cmp(&self.glyph)
	}
}
